#ifndef __OCPP_CesiumMatrix4_H__
#define __OCPP_CesiumMatrix4_H__

#include "Cartesian3.h"
#include "Cartesian4.h"
#include "Matrix3.h"
#include "Quaternion.h"

namespace OCPP
{
	namespace Cesium
	{
		class _CesiumExport Matrix4
		{
		public:
			std::vector<double> data = {
				0.0,0.0,0.0,0.0,
				0.0,0.0,0.0,0.0,
				0.0,0.0,0.0,0.0,
				0.0,0.0,0.0,0.0
			};

			Matrix4(DoubleVector dv)
			{
				this->data[0] = dv[0];
				this->data[1] = dv[1];
				this->data[2] = dv[2];
				this->data[3] = dv[3];
				this->data[4] = dv[4];
				this->data[5] = dv[5];
				this->data[6] = dv[6];
				this->data[7] = dv[7];
				this->data[8] = dv[8];
				this->data[9] = dv[9];
				this->data[10] = dv[10];
				this->data[11] = dv[11];
				this->data[12] = dv[12];
				this->data[13] = dv[13];
				this->data[14] = dv[14];
				this->data[15] = dv[15];
			}

			Matrix4(
				double column0Row0 = 0.0,
				double column1Row0 = 0.0,
				double column2Row0 = 0.0,
				double column3Row0 = 0.0,
				double column0Row1 = 0.0,
				double column1Row1 = 0.0,
				double column2Row1 = 0.0,
				double column3Row1 = 0.0,
				double column0Row2 = 0.0,
				double column1Row2 = 0.0,
				double column2Row2 = 0.0,
				double column3Row2 = 0.0,
				double column0Row3 = 0.0,
				double column1Row3 = 0.0,
				double column2Row3 = 0.0,
				double column3Row3 = 0.0)
			{
				this->data[0] = column0Row0;
				this->data[1] = column1Row0;
				this->data[2] = column2Row0;
				this->data[3] = column3Row0;
				this->data[4] = column0Row1;
				this->data[5] = column1Row1;
				this->data[6] = column2Row1;
				this->data[7] = column3Row1;
				this->data[8] = column0Row2;
				this->data[9] = column1Row2;
				this->data[10] = column2Row2;
				this->data[11] = column3Row2;
				this->data[12] = column0Row3;
				this->data[13] = column1Row3;
				this->data[14] = column2Row3;
				this->data[15] = column3Row3;
			}

			Matrix4& operator = (const Matrix4& fScaler)
			{
				data = fScaler.data;

				return (Matrix4&)*this;
			}

			double& operator[](size_t i)
			{
				return this->data[i];
			}

			int packedLength = 16;

			/**
			 * Stores the provided instance into the provided array1.
			 *
			 * @param {Matrix4} value The value to pack.
			 * @param {number[]} array1 The array1 to pack into.
			 * @param {number} [startingIndex=0] The index into the array1 at which to start packing the elements.
			 *
			 * @returns {number[]} The array1 that was packed into
			 */
			static DoubleVector pack(Matrix4& value, DoubleVector array1, size_t startingIndex = 0) {

				array1[startingIndex++] = value.data[0];
				array1[startingIndex++] = value.data[1];
				array1[startingIndex++] = value.data[2];
				array1[startingIndex++] = value.data[3];
				array1[startingIndex++] = value.data[4];
				array1[startingIndex++] = value.data[5];
				array1[startingIndex++] = value.data[6];
				array1[startingIndex++] = value.data[7];
				array1[startingIndex++] = value.data[8];
				array1[startingIndex++] = value[9];
				array1[startingIndex++] = value[10];
				array1[startingIndex++] = value[11];
				array1[startingIndex++] = value[12];
				array1[startingIndex++] = value[13];
				array1[startingIndex++] = value[14];
				array1[startingIndex] = value[15];

				return array1;
			};

			/**
			 * Retrieves an instance from a packed array1.
			 *
			 * @param {number[]} array1 The packed array1.
			 * @param {number} [startingIndex=0] The starting index of the element to be unpacked.
			 * @param {Matrix4} [result] The object into which to store the result.
			 * @returns {Matrix4} The modified result parameter or a Matrix4 instance if one was not provided.
			 */
			static Matrix4 unpack(DoubleVector array1, size_t startingIndex = 0) {

				Matrix4 result;

				result.data[0] = array1[startingIndex++];
				result.data[1] = array1[startingIndex++];
				result.data[2] = array1[startingIndex++];
				result.data[3] = array1[startingIndex++];
				result.data[4] = array1[startingIndex++];
				result.data[5] = array1[startingIndex++];
				result.data[6] = array1[startingIndex++];
				result.data[7] = array1[startingIndex++];
				result.data[8] = array1[startingIndex++];
				result[9] = array1[startingIndex++];
				result[10] = array1[startingIndex++];
				result[11] = array1[startingIndex++];
				result[12] = array1[startingIndex++];
				result[13] = array1[startingIndex++];
				result[14] = array1[startingIndex++];
				result[15] = array1[startingIndex];
				return result;
			};

			/**
			 * Flattens an array1 of Matrix3s into an array1 of components. The components
			 * are stored in column-major order.
			 *
			 * @param {Matrix4[]} array1 The array1 of matrices to pack.
			 * @param {number[]} [result] The array1 onto which to store the result. If this is a typed array1, it must have array1.length * 9 components, else a {@link DeveloperError} will be thrown. If it is a regular array1, it will be resized to have (array1.length * 9) elements.
			 * @returns {number[]} The packed array1.
			 */
			static DoubleVector packArray(std::vector<Matrix4> array1) {
				auto length = array1.size();
				auto resultLength = length * 16;
				DoubleVector result;
				result.resize(resultLength);

				for (auto i = 0; i < length; ++i) {
					Matrix4::pack(array1[i], result, i * 16);
				}
				return result;
			};

			/**
			 * Unpacks an array1 of column-major matrix components into an array1 of Matrix3s.
			 *
			 * @param {number[]} array1 The array1 of components to unpack.
			 * @param {Matrix4[]} [result] The array1 onto which to store the result.
			 * @returns {Matrix4[]} The unpacked array1.
			 */
			static std::vector<Matrix4> unpackArray(DoubleVector array1) {
				auto length = array1.size();
				std::vector<Matrix4> result;
				result.resize(length / 16);

				for (auto i = 0; i < length; i += 16) {
					auto index = i / 16;
					result[index] = Matrix4::unpack(array1, i);
				}
				return result;
			};

			/**
			 * Creates a Matrix4 from 9 consecutive elements in an array1.
			 *
			 * @function
			 * @param {number[]} array1 The array1 whose 9 consecutive elements correspond to the positions of the matrix.  Assumes column-major order.
			 * @param {number} [startingIndex=0] The offset into the array1 of the first element, which corresponds to first column first row position in the matrix.
			 * @param {Matrix4} [result] The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter or a Matrix4 instance if one was not provided.
			 *
			 * @example
			 * // Create the Matrix4:
			 * // [1.0, 2.0, 3.0]
			 * // [1.0, 2.0, 3.0]
			 * // [1.0, 2.0, 3.0]
			 *
			 * const v = [1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0];
			 * const m = Cesium.static Matrix4 fromArray(v);
			 *
			 * // Create same Matrix4 with using an offset into an array1
			 * const v2 = [0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0];
			 * const m2 = Cesium.static Matrix4 fromArray(v2, 2);
			 */
			static Matrix4 fromArray(DoubleVector array1, size_t startingIndex = 0) {
				return Matrix4::unpack(array1, startingIndex);
			};
			/**
			 * Creates a Matrix4 instance from a column-major order array1.
			 *
			 * @param {number[]} values The column-major order array1.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a Matrix4 instance if one was not provided.
			 */
			static Matrix4 fromColumnMajorArray(DoubleVector values) {
				return Matrix4(values);
			};

			/**
			 * Creates a Matrix4 instance from a row-major order array1.
			 * The resulting matrix will be in column-major order.
			 *
			 * @param {number[]} values The row-major order array1.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a Matrix4 instance if one was not provided.
			 */
			static Matrix4 fromRowMajorArray(DoubleVector values) {
				return Matrix4(
					values[0],
					values[1],
					values[2],
					values[3],
					values[4],
					values[5],
					values[6],
					values[7],
					values[8],
					values[9],
					values[10],
					values[11],
					values[12],
					values[13],
					values[14],
					values[15]

				);
			};

			/**
			 * Computes a Matrix4 instance from a Matrix3 representing the rotation
			 * and a Cartesian3 representing the translation.
			 *
			 * @param {Matrix3} rotation The upper left portion of the matrix representing the rotation.
			 * @param {Cartesian3} [translation=Cartesian3.ZERO] The upper right portion of the matrix representing the translation.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
			 */
			static Matrix4 fromRotationTranslation(Matrix3 rotation, Cartesian3 translation = Cartesian3::ZERO) {
				return  Matrix4(
					rotation[0],
					rotation[3],
					rotation[6],
					translation.x,
					rotation[1],
					rotation[4],
					rotation[7],
					translation.y,
					rotation[2],
					rotation[5],
					rotation[8],
					translation.z,
					0.0,
					0.0,
					0.0,
					1.0
				);
			};

			/**
			 * Computes a Matrix4 instance from a translation, rotation, and scale (TRS)
			 * representation with the rotation represented as a quaternion.
			 *
			 * @param {Cartesian3} translation The translation transformation.
			 * @param {Quaternion} rotation The rotation transformation.
			 * @param {Cartesian3} scale The non-uniform scale transformation.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
			 *
			 * @example
			 * const result = Cesium.Matrix4.fromTranslationQuaternionRotationScale(
			 *   new Cesium.Cartesian3(1.0, 2.0, 3.0), // translation
			 *   Cesium.Quaternion.IDENTITY,           // rotation
			 *   new Cesium.Cartesian3(7.0, 8.0, 9.0), // scale
			 *   result);
			 */
			static Matrix4 fromTranslationQuaternionRotationScale(
				Cartesian3 translation,
				Quaternion rotation,
				Cartesian3 scale
			) {
				auto scaleX = scale.x;
				auto scaleY = scale.y;
				auto scaleZ = scale.z;

				auto x2 = rotation.x * rotation.x;
				auto xy = rotation.x * rotation.y;
				auto xz = rotation.x * rotation.z;
				auto xw = rotation.x * rotation.w;
				auto y2 = rotation.y * rotation.y;
				auto yz = rotation.y * rotation.z;
				auto yw = rotation.y * rotation.w;
				auto z2 = rotation.z * rotation.z;
				auto zw = rotation.z * rotation.w;
				auto w2 = rotation.w * rotation.w;

				auto m00 = x2 - y2 - z2 + w2;
				auto m01 = 2.0 * (xy - zw);
				auto m02 = 2.0 * (xz + yw);

				auto m10 = 2.0 * (xy + zw);
				auto m11 = -x2 + y2 - z2 + w2;
				auto m12 = 2.0 * (yz - xw);

				auto m20 = 2.0 * (xz - yw);
				auto m21 = 2.0 * (yz + xw);
				auto m22 = -x2 - y2 + z2 + w2;

				Matrix4 result;
				result[0] = m00 * scaleX;
				result[1] = m10 * scaleX;
				result[2] = m20 * scaleX;
				result[3] = 0.0;
				result[4] = m01 * scaleY;
				result[5] = m11 * scaleY;
				result[6] = m21 * scaleY;
				result[7] = 0.0;
				result[8] = m02 * scaleZ;
				result[9] = m12 * scaleZ;
				result[10] = m22 * scaleZ;
				result[11] = 0.0;
				result[12] = translation.x;
				result[13] = translation.y;
				result[14] = translation.z;
				result[15] = 1.0;

				return result;
			};

			/**
			 * Creates a Matrix4 instance from a {@link TranslationRotationScale} instance.
			 *
			 * @param {TranslationRotationScale} translationRotationScale The instance.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
			 */
			 //static Matrix4 fromTranslationRotationScale(
			 //	TranslationRotationScale translationRotationScale
			 //) {
			 //	return Matrix4::fromTranslationQuaternionRotationScale(
			 //		translationRotationScale.translation,
			 //		translationRotationScale.rotation,
			 //		translationRotationScale.scale
			 //	);
			 //};

			 /**
			  * Creates a Matrix4 instance from a Cartesian3 representing the translation.
			  *
			  * @param {Cartesian3} translation The upper right portion of the matrix representing the translation.
			  * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			  * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
			  *
			  * @see Matrix4.multiplyByTranslation
			  */
			static Matrix4 fromTranslation(Cartesian3 translation) {
				return Matrix4::fromRotationTranslation(Matrix3::IDENTITY, translation);
			};

			/**
			 * Computes a 3x3 rotation matrix from the provided quaternion.
			 *
			 * @param {Quaternion} quaternion the quaternion to use.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The 3x3 rotation matrix from this quaternion.
			 */
			 //static Matrix4 fromQuaternion(Quaternion quaternion) {
			 //	auto x2 = quaternion.x * quaternion.x;
			 //	auto xy = quaternion.x * quaternion.y;
			 //	auto xz = quaternion.x * quaternion.z;
			 //	auto xw = quaternion.x * quaternion.w;
			 //	auto y2 = quaternion.y * quaternion.y;
			 //	auto yz = quaternion.y * quaternion.z;
			 //	auto yw = quaternion.y * quaternion.w;
			 //	auto z2 = quaternion.z * quaternion.z;
			 //	auto zw = quaternion.z * quaternion.w;
			 //	auto w2 = quaternion.w * quaternion.w;

			 //	auto m00 = x2 - y2 - z2 + w2;
			 //	auto m01 = 2.0 * (xy - zw);
			 //	auto m02 = 2.0 * (xz + yw);

			 //	auto m10 = 2.0 * (xy + zw);
			 //	auto m11 = -x2 + y2 - z2 + w2;
			 //	auto m12 = 2.0 * (yz - xw);

			 //	auto m20 = 2.0 * (xz - yw);
			 //	auto m21 = 2.0 * (yz + xw);
			 //	auto m22 = -x2 - y2 + z2 + w2;

			 //	return Matrix4(m00, m01, m02, m10, m11, m12, m20, m21, m22);
			 //};

			 /**
			  * Computes a 3x3 rotation matrix from the provided headingPitchRoll. (see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles )
			  *
			  * @param {HeadingPitchRoll} headingPitchRoll the headingPitchRoll to use.
			  * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			  * @returns {Matrix4} The 3x3 rotation matrix from this headingPitchRoll.
			  */
			  //static Matrix4 fromHeadingPitchRoll(HeadingPitchRoll headingPitchRoll) {
			  //	auto cosTheta = cos(-headingPitchRoll.pitch);
			  //	auto cosPsi = cos(-headingPitchRoll.heading);
			  //	auto cosPhi = cos(headingPitchRoll.roll);
			  //	auto sinTheta = sin(-headingPitchRoll.pitch);
			  //	auto sinPsi = sin(-headingPitchRoll.heading);
			  //	auto sinPhi = sin(headingPitchRoll.roll);

			  //	auto m00 = cosTheta * cosPsi;
			  //	auto m01 = -cosPhi * sinPsi + sinPhi * sinTheta * cosPsi;
			  //	auto m02 = sinPhi * sinPsi + cosPhi * sinTheta * cosPsi;

			  //	auto m10 = cosTheta * sinPsi;
			  //	auto m11 = cosPhi * cosPsi + sinPhi * sinTheta * sinPsi;
			  //	auto m12 = -sinPhi * cosPsi + cosPhi * sinTheta * sinPsi;

			  //	auto m20 = -sinTheta;
			  //	auto m21 = sinPhi * cosTheta;
			  //	auto m22 = cosPhi * cosTheta;

			  //	return Matrix4(m00, m01, m02, m10, m11, m12, m20, m21, m22);
			  //};

			  /**
			   * Computes a Matrix4 instance representing a non-uniform scale.
			   *
			   * @param {Cartesian3} scale The x, y, and z scale factors.
			   * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			   * @returns {Matrix4} The modified result parameter, or a Matrix4 instance if one was not provided.
			   *
			   * @example
			   * // Creates
			   * //   [7.0, 0.0, 0.0]
			   * //   [0.0, 8.0, 0.0]
			   * //   [0.0, 0.0, 9.0]
			   * const m = Cesium.static Matrix4 fromScale(new Cesium.Cartesian3(7.0, 8.0, 9.0));
			   */
			static Matrix4 fromScale(Cartesian3 scale) {
				return Matrix4(
					scale.x,
					0.0,
					0.0,
					0.0,
					0.0,
					scale.y,
					0.0,
					0.0,
					0.0,
					0.0,
					scale.z,
					0.0,
					0.0,
					0.0,
					0.0,
					1.0);
			};

			/**
			 * Computes a Matrix4 instance representing a uniform scale.
			 *
			 * @param {number} scale The uniform scale factor.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a Matrix4 instance if one was not provided.
			 *
			 * @example
			 * // Creates
			 * //   [2.0, 0.0, 0.0]
			 * //   [0.0, 2.0, 0.0]
			 * //   [0.0, 0.0, 2.0]
			 * const m = Cesium.static Matrix4 fromUniformScale(2.0);
			 */
			static Matrix4 fromUniformScale(double scale) {
				return Matrix4(scale,
					0.0,
					0.0,
					0.0,
					0.0,
					scale,
					0.0,
					0.0,
					0.0,
					0.0,
					scale,
					0.0,
					0.0,
					0.0,
					0.0,
					1.0);
			};

			/**
			 * Creates a rotation matrix.
			 *
			 * @param {Matrix3} rotation The rotation matrix.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
			 */
			static Matrix4 fromRotation(Matrix3 rotation) {
				Matrix4 result;

				result[0] = rotation[0];
				result[1] = rotation[1];
				result[2] = rotation[2];
				result[3] = 0.0;

				result[4] = rotation[3];
				result[5] = rotation[4];
				result[6] = rotation[5];
				result[7] = 0.0;

				result[8] = rotation[6];
				result[9] = rotation[7];
				result[10] = rotation[8];
				result[11] = 0.0;

				result[12] = 0.0;
				result[13] = 0.0;
				result[14] = 0.0;
				result[15] = 1.0;

				return result;
			};

			///**
			// * Computes a Matrix4 instance from a Camera.
			// *
			// * @param {Camera} camera The camera to use.
			// * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			// * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
			// */
			//Matrix4.fromCamera(camera, result) {
			//	//>>includeStart('debug', pragmas.debug);
			//	Check.typeOf.object("camera", camera);
			//	//>>includeEnd('debug');

			//	const position = camera.position;
			//	const direction = camera.direction;
			//	const up = camera.up;

			//	//>>includeStart('debug', pragmas.debug);
			//	Check.typeOf.object("camera.position", position);
			//	Check.typeOf.object("camera.direction", direction);
			//	Check.typeOf.object("camera.up", up);
			//	//>>includeEnd('debug');

			//	Cartesian3.normalize(direction, fromCameraF);
			//	Cartesian3.normalize(
			//		Cartesian3.cross(fromCameraF, up, fromCameraR),
			//		fromCameraR
			//	);
			//	Cartesian3.normalize(
			//		Cartesian3.cross(fromCameraR, fromCameraF, fromCameraU),
			//		fromCameraU
			//	);

			//	const sX = fromCameraR.x;
			//	const sY = fromCameraR.y;
			//	const sZ = fromCameraR.z;
			//	const fX = fromCameraF.x;
			//	const fY = fromCameraF.y;
			//	const fZ = fromCameraF.z;
			//	const uX = fromCameraU.x;
			//	const uY = fromCameraU.y;
			//	const uZ = fromCameraU.z;
			//	const positionX = position.x;
			//	const positionY = position.y;
			//	const positionZ = position.z;
			//	const t0 = sX * -positionX + sY * -positionY + sZ * -positionZ;
			//	const t1 = uX * -positionX + uY * -positionY + uZ * -positionZ;
			//	const t2 = fX * positionX + fY * positionY + fZ * positionZ;

			//	// The code below this comment is an optimized
			//	// version of the commented lines.
			//	// Rather that create two matrices and then multiply,
			//	// we just bake in the multiplcation as part of creation.
			//	// const rotation = new Matrix4(
			//	//                 sX,  sY,  sZ, 0.0,
			//	//                 uX,  uY,  uZ, 0.0,
			//	//                -fX, -fY, -fZ, 0.0,
			//	//                 0.0,  0.0,  0.0, 1.0);
			//	// const translation = new Matrix4(
			//	//                 1.0, 0.0, 0.0, -position.x,
			//	//                 0.0, 1.0, 0.0, -position.y,
			//	//                 0.0, 0.0, 1.0, -position.z,
			//	//                 0.0, 0.0, 0.0, 1.0);
			//	// return rotation.multiply(translation);
			//	if (!defined(result)) {
			//		return new Matrix4(
			//			sX,
			//			sY,
			//			sZ,
			//			t0,
			//			uX,
			//			uY,
			//			uZ,
			//			t1,
			//			-fX,
			//			-fY,
			//			-fZ,
			//			t2,
			//			0.0,
			//			0.0,
			//			0.0,
			//			1.0
			//		);
			//	}
			//	result[0] = sX;
			//	result[1] = uX;
			//	result[2] = -fX;
			//	result[3] = 0.0;
			//	result[4] = sY;
			//	result[5] = uY;
			//	result[6] = -fY;
			//	result[7] = 0.0;
			//	result[8] = sZ;
			//	result[9] = uZ;
			//	result[10] = -fZ;
			//	result[11] = 0.0;
			//	result[12] = t0;
			//	result[13] = t1;
			//	result[14] = t2;
			//	result[15] = 1.0;
			//	return result;
			//};

			/**
			 * Computes a Matrix4 instance representing a perspective transformation matrix.
			 *
			 * @param {number} fovY The field of view along the Y axis in radians.
			 * @param {number} aspectRatio The aspect ratio.
			 * @param {number} near The distance to the near plane in meters.
			 * @param {number} far The distance to the far plane in meters.
			 * @param {Matrix4} result The object in which the result will be stored.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @exception {DeveloperError} fovY must be in (0, PI].
			 * @exception {DeveloperError} aspectRatio must be greater than zero.
			 * @exception {DeveloperError} near must be greater than zero.
			 * @exception {DeveloperError} far must be greater than zero.
			 */
			static Matrix4 computePerspectiveFieldOfView(
				double fovY,
				double aspectRatio,
				double near,
				double far
			);


			/**
			 * Computes a Matrix4 instance representing an orthographic transformation matrix.
			 *
			 * @param {number} left The number of meters to the left of the camera that will be in view.
			 * @param {number} right The number of meters to the right of the camera that will be in view.
			 * @param {number} bottom The number of meters below of the camera that will be in view.
			 * @param {number} top The number of meters above of the camera that will be in view.
			 * @param {number} near The distance to the near plane in meters.
			 * @param {number} far The distance to the far plane in meters.
			 * @param {Matrix4} result The object in which the result will be stored.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 computeOrthographicOffCenter(
				double left,
				double right,
				double bottom,
				double top,
				double near,
				double far
			);

			/**
			 * Computes a Matrix4 instance representing an off center perspective transformation.
			 *
			 * @param {number} left The number of meters to the left of the camera that will be in view.
			 * @param {number} right The number of meters to the right of the camera that will be in view.
			 * @param {number} bottom The number of meters below of the camera that will be in view.
			 * @param {number} top The number of meters above of the camera that will be in view.
			 * @param {number} near The distance to the near plane in meters.
			 * @param {number} far The distance to the far plane in meters.
			 * @param {Matrix4} result The object in which the result will be stored.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 computePerspectiveOffCenter(
				double left,
				double right,
				double bottom,
				double top,
				double near,
				double far
			);

			/**
			 * Computes a Matrix4 instance representing an infinite off center perspective transformation.
			 *
			 * @param {number} left The number of meters to the left of the camera that will be in view.
			 * @param {number} right The number of meters to the right of the camera that will be in view.
			 * @param {number} bottom The number of meters below of the camera that will be in view.
			 * @param {number} top The number of meters above of the camera that will be in view.
			 * @param {number} near The distance to the near plane in meters.
			 * @param {Matrix4} result The object in which the result will be stored.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 computeInfinitePerspectiveOffCenter(
				double left,
				double right,
				double bottom,
				double top,
				double near
			);

			/**
			 * Computes a Matrix4 instance that transforms from normalized device coordinates to window coordinates.
			 *
			 * @param {object} [viewport = { x : 0.0, y : 0.0, width : 0.0, height : 0.0 }] The viewport's corners as shown in Example 1.
			 * @param {number} [nearDepthRange=0.0] The near plane distance in window coordinates.
			 * @param {number} [farDepthRange=1.0] The far plane distance in window coordinates.
			 * @param {Matrix4} [result] The object in which the result will be stored.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @example
			 * // Create viewport transformation using an explicit viewport and depth range.
			 * const m = Cesium.Matrix4.computeViewportTransformation({
			 *     x : 0.0,
			 *     y : 0.0,
			 *     width : 1024.0,
			 *     height : 768.0
			 * }, 0.0, 1.0, new Cesium.Matrix4());
			 */
			static Matrix4 computeViewportTransformation(
				Cartesian4 viewport,
				double nearDepthRange = 0.0,
				double farDepthRange = 1.0
			) {
				Matrix4 result;
				auto x = viewport.x;
				auto y = viewport.y;
				//auto width = viewport.width;
				//auto height = viewport.height;
				auto width = viewport.z;
				auto height = viewport.w;

				auto halfWidth = width * 0.5;
				auto halfHeight = height * 0.5;
				auto halfDepth = (farDepthRange - nearDepthRange) * 0.5;

				auto column0Row0 = halfWidth;
				auto column1Row1 = halfHeight;
				auto column2Row2 = halfDepth;
				auto column3Row0 = x + halfWidth;
				auto column3Row1 = y + halfHeight;
				auto column3Row2 = nearDepthRange + halfDepth;
				auto column3Row3 = 1.0;

				result[0] = column0Row0;
				result[1] = 0.0;
				result[2] = 0.0;
				result[3] = 0.0;
				result[4] = 0.0;
				result[5] = column1Row1;
				result[6] = 0.0;
				result[7] = 0.0;
				result[8] = 0.0;
				result[9] = 0.0;
				result[10] = column2Row2;
				result[11] = 0.0;
				result[12] = column3Row0;
				result[13] = column3Row1;
				result[14] = column3Row2;
				result[15] = column3Row3;

				return result;
			};

			/**
			 * Computes a Matrix4 instance that transforms from world space to view space.
			 *
			 * @param {Cartesian3} position The position of the camera.
			 * @param {Cartesian3} direction The forward direction.
			 * @param {Cartesian3} up The up direction.
			 * @param {Cartesian3} right The right direction.
			 * @param {Matrix4} result The object in which the result will be stored.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 computeView(Cartesian3 position, Cartesian3 direction, Cartesian3 up, Cartesian3 right) {
				Matrix4 result;
				result[0] = right.x;
				result[1] = up.x;
				result[2] = -direction.x;
				result[3] = 0.0;
				result[4] = right.y;
				result[5] = up.y;
				result[6] = -direction.y;
				result[7] = 0.0;
				result[8] = right.z;
				result[9] = up.z;
				result[10] = -direction.z;
				result[11] = 0.0;
				result[12] = -Cartesian3::dot(right, position);
				result[13] = -Cartesian3::dot(up, position);
				result[14] = Cartesian3::dot(direction, position);
				result[15] = 1.0;
				return result;
			};

			/**
			 * Computes a Matrix4 instance representing the cross product equivalent matrix of a Cartesian3 vector.
			 *
			 * @param {Cartesian3} vector the vector on the left hand side of the cross product operation.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a Matrix4 instance if one was not provided.
			 *
			 * @example
			 * // Creates
			 * //   [0.0, -9.0,  8.0]
			 * //   [9.0,  0.0, -7.0]
			 * //   [-8.0, 7.0,  0.0]
			 * const m = Cesium.static Matrix4 fromCrossProduct(new Cesium.Cartesian3(7.0, 8.0, 9.0));
			 */
			static Matrix4 fromCrossProduct(Cartesian3 vector) {
				return Matrix4(
					0.0,
					-vector.z,
					vector.y,
					vector.z,
					0.0,
					-vector.x,
					-vector.y,
					vector.x,
					0.0
				);
			};

			/**
			 * Creates a rotation matrix around the x-axis.
			 *
			 * @param {number} angle The angle, in radians, of the rotation.  Positive angles are counterclockwise.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a Matrix4 instance if one was not provided.
			 *
			 * @example
			 * // Rotate a point 45 degrees counterclockwise around the x-axis.
			 * const p = new Cesium.Cartesian3(5, 6, 7);
			 * const m = Cesium.static Matrix4 fromRotationX(Cesium.Math.toRadians(45.0));
			 * const rotated = Cesium.Matrix4::multiplyByVector(m, p, new Cesium.Cartesian3());
			 */
			static Matrix4 fromRotationX(double angle) {
				auto cosAngle = cos(angle);
				auto sinAngle = sin(angle);

				return Matrix4(
					1.0,
					0.0,
					0.0,
					0.0,
					cosAngle,
					-sinAngle,
					0.0,
					sinAngle,
					cosAngle
				);
			};

			/**
			 * Creates a rotation matrix around the y-axis.
			 *
			 * @param {number} angle The angle, in radians, of the rotation.  Positive angles are counterclockwise.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a Matrix4 instance if one was not provided.
			 *
			 * @example
			 * // Rotate a point 45 degrees counterclockwise around the y-axis.
			 * const p = new Cesium.Cartesian3(5, 6, 7);
			 * const m = Cesium.static Matrix4 fromRotationY(Cesium.Math.toRadians(45.0));
			 * const rotated = Cesium.Matrix4::multiplyByVector(m, p, new Cesium.Cartesian3());
			 */
			static Matrix4 fromRotationY(double angle) {
				auto cosAngle = cos(angle);
				auto sinAngle = sin(angle);

				return  Matrix4(
					cosAngle,
					0.0,
					sinAngle,
					0.0,
					1.0,
					0.0,
					-sinAngle,
					0.0,
					cosAngle
				);
			};

			/**
			 * Creates a rotation matrix around the z-axis.
			 *
			 * @param {number} angle The angle, in radians, of the rotation.  Positive angles are counterclockwise.
			 * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
			 * @returns {Matrix4} The modified result parameter, or a Matrix4 instance if one was not provided.
			 *
			 * @example
			 * // Rotate a point 45 degrees counterclockwise around the z-axis.
			 * const p = new Cesium.Cartesian3(5, 6, 7);
			 * const m = Cesium.static Matrix4 fromRotationZ(Cesium.Math.toRadians(45.0));
			 * const rotated = Cesium.Matrix4::multiplyByVector(m, p, new Cesium.Cartesian3());
			 */
			static Matrix4 fromRotationZ(double angle) {
				auto cosAngle = cos(angle);
				auto sinAngle = sin(angle);

				return  Matrix4(
					cosAngle,
					-sinAngle,
					0.0,
					sinAngle,
					cosAngle,
					0.0,
					0.0,
					0.0,
					1.0
				);
			};

			/**
			 * Creates an Array from the provided Matrix4 instance.
			 * The array1 will be in column-major order.
			 *
			 * @param {Matrix4} matrix The matrix to use..
			 * @param {number[]} [result] The Array onto which to store the result.
			 * @returns {number[]} The modified Array parameter or a new Array instance if one was not provided.
			 */
			DoubleVector toArray(Matrix4& matrix) {
				return DoubleVector{
					 matrix[0],
					  matrix[1],
					  matrix[2],
					  matrix[3],
					  matrix[4],
					  matrix[5],
					  matrix[6],
					  matrix[7],
					  matrix[8],
					  matrix[9],
					  matrix[10],
					  matrix[11],
					  matrix[12],
					  matrix[13],
					  matrix[14],
					  matrix[15]
				};
			};

			/**
			 * Computes the array index of the element at the provided row and column.
			 *
			 * @param {number} row The zero-based index of the row.
			 * @param {number} column The zero-based index of the column.
			 * @returns {number} The index of the element at the provided row and column.
			 *
			 * @exception {DeveloperError} row must be 0, 1, 2, or 3.
			 * @exception {DeveloperError} column must be 0, 1, 2, or 3.
			 *
			 * @example
			 * const myMatrix = new Cesium.Matrix4();
			 * const column1Row0Index = Cesium.Matrix4.getElementIndex(1, 0);
			 * const column1Row0 = myMatrix[column1Row0Index];
			 * myMatrix[column1Row0Index] = 10.0;
			 */
			static int getElementIndex(int column, int row) {
				return column * 4 + row;
			};

			/**
			 * Retrieves a copy of the matrix column at the provided index as a Cartesian3 instance.
			 *
			 * @param {Matrix4} matrix The matrix to use.
			 * @param {number} index The zero-based index of the column to retrieve.
			 * @param {Cartesian3} result The object onto which to store the result.
			 * @returns {Cartesian3} The modified result parameter.
			 *
			 * @exception {DeveloperError} index must be 0, 1, or 2.
			 */
			static Cartesian4 getColumn(Matrix4& matrix, int index) {
				auto startIndex = index * 3;
				auto x = matrix.data[startIndex];
				auto y = matrix.data[startIndex + 1];
				auto z = matrix.data[startIndex + 2];
				auto w = matrix.data[startIndex + 3];
				Cartesian4 result;
				result.x = x;
				result.y = y;
				result.z = z;
				result.w = z;
				return result;
			};

			/**
			 * Computes a new matrix that replaces the specified column in the provided matrix with the provided Cartesian3 instance.
			 *
			 * @param {Matrix4} matrix The matrix to use.
			 * @param {number} index The zero-based index of the column to set.
			 * @param {Cartesian3} cartesian The Cartesian whose values will be assigned to the specified column.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @exception {DeveloperError} index must be 0, 1, or 2.
			 */
			static Matrix4 setColumn(Matrix4& matrix, int index, Cartesian4 cartesian) {
				Matrix4 result = matrix;
				auto startIndex = index * 4;
				result[startIndex] = cartesian.x;
				result[startIndex + 1] = cartesian.y;
				result[startIndex + 2] = cartesian.z;
				result[startIndex + 3] = cartesian.w;
				return result;
			};

			/**
			 * Retrieves a copy of the matrix row at the provided index as a Cartesian3 instance.
			 *
			 * @param {Matrix4} matrix The matrix to use.
			 * @param {number} index The zero-based index of the row to retrieve.
			 * @param {Cartesian3} result The object onto which to store the result.
			 * @returns {Cartesian3} The modified result parameter.
			 *
			 * @exception {DeveloperError} index must be 0, 1, or 2.
			 */
			static Cartesian4 getRow(Matrix4& matrix, int index) {
				auto x = matrix.data[index];
				auto y = matrix.data[index + 4];
				auto z = matrix.data[index + 8];
				auto w = matrix.data[index + 12];
				Cartesian4 result;
				result.x = x;
				result.y = y;
				result.z = z;
				result.w = w;
				return result;
			};

			/**
			 * Computes a new matrix that replaces the specified row in the provided matrix with the provided Cartesian3 instance.
			 *
			 * @param {Matrix4} matrix The matrix to use.
			 * @param {number} index The zero-based index of the row to set.
			 * @param {Cartesian3} cartesian The Cartesian whose values will be assigned to the specified row.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @exception {DeveloperError} index must be 0, 1, or 2.
			 */
			static Matrix4 setRow(Matrix4& matrix, int index, Cartesian4 cartesian) {
				Matrix4 result = matrix;
				result.data[index] = cartesian.x;
				result.data[index + 4] = cartesian.y;
				result.data[index + 8] = cartesian.z;
				result.data[index + 12] = cartesian.w;
				return result;
			};
			/**
			 * Computes a new matrix that replaces the translation in the rightmost column of the provided
			 * matrix with the provided translation. This assumes the matrix is an affine transformation.
			 *
			 * @param {Matrix4} matrix The matrix to use.
			 * @param {Cartesian3} translation The translation that replaces the translation of the provided matrix.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 setTranslation(Matrix4 matrix, Cartesian3 translation) {
				Matrix4 result;

				result[0] = matrix[0];
				result[1] = matrix[1];
				result[2] = matrix[2];
				result[3] = matrix[3];

				result[4] = matrix[4];
				result[5] = matrix[5];
				result[6] = matrix[6];
				result[7] = matrix[7];

				result[8] = matrix[8];
				result[9] = matrix[9];
				result[10] = matrix[10];
				result[11] = matrix[11];

				result[12] = translation.x;
				result[13] = translation.y;
				result[14] = translation.z;
				result[15] = matrix[15];

				return result;
			};

			/**
			 * Computes a new matrix that replaces the scale with the provided scale.
			 * This assumes the matrix is an affine transformation.
			 *
			 * @param {Matrix4} matrix The matrix to use.
			 * @param {Cartesian3} scale The scale that replaces the scale of the provided matrix.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @see Matrix4::setUniformScale
			 * @see static Matrix4 fromScale
			 * @see static Matrix4 fromUniformScale
			 * @see Matrix4::multiplyByScale
			 * @see Matrix4::multiplyByUniformScale
			 * @see Matrix4::getScale
			 */
			static Matrix4 setScale(Matrix4& matrix, Cartesian3 scale) {
				auto existingScale = Matrix4::getScale(matrix);
				auto scaleRatioX = scale.x / existingScale.x;
				auto scaleRatioY = scale.y / existingScale.y;
				auto scaleRatioZ = scale.z / existingScale.z;
				Matrix4 result;
				result[0] = matrix[0] * scaleRatioX;
				result[1] = matrix[1] * scaleRatioX;
				result[2] = matrix[2] * scaleRatioX;
				result[3] = matrix[3];

				result[4] = matrix[4] * scaleRatioY;
				result[5] = matrix[5] * scaleRatioY;
				result[6] = matrix[6] * scaleRatioY;
				result[7] = matrix[7];

				result[8] = matrix[8] * scaleRatioZ;
				result[9] = matrix[9] * scaleRatioZ;
				result[10] = matrix[10] * scaleRatioZ;
				result[11] = matrix[11];

				result[12] = matrix[12];
				result[13] = matrix[13];
				result[14] = matrix[14];
				result[15] = matrix[15];

				return result;
			};

			/**
			 * Computes a new matrix that replaces the scale with the provided uniform scale.
			 * This assumes the matrix is an affine transformation.
			 *
			 * @param {Matrix4} matrix The matrix to use.
			 * @param {number} scale The uniform scale that replaces the scale of the provided matrix.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @see Matrix4::setScale
			 * @see static Matrix4 fromScale
			 * @see static Matrix4 fromUniformScale
			 * @see Matrix4::multiplyByScale
			 * @see Matrix4::multiplyByUniformScale
			 * @see Matrix4::getScale
			 */
			static Matrix4 setUniformScale(Matrix4& matrix, double scale) {
				Matrix4 result;
				auto existingScale = Matrix4::getScale(matrix);
				auto scaleRatioX = scale / existingScale.x;
				auto scaleRatioY = scale / existingScale.y;
				auto scaleRatioZ = scale / existingScale.z;

				result[0] = matrix[0] * scaleRatioX;
				result[1] = matrix[1] * scaleRatioX;
				result[2] = matrix[2] * scaleRatioX;
				result[3] = matrix[3];

				result[4] = matrix[4] * scaleRatioY;
				result[5] = matrix[5] * scaleRatioY;
				result[6] = matrix[6] * scaleRatioY;
				result[7] = matrix[7];

				result[8] = matrix[8] * scaleRatioZ;
				result[9] = matrix[9] * scaleRatioZ;
				result[10] = matrix[10] * scaleRatioZ;
				result[11] = matrix[11];

				result[12] = matrix[12];
				result[13] = matrix[13];
				result[14] = matrix[14];
				result[15] = matrix[15];

				return result;
			};

			/**
			 * Extracts the non-uniform scale assuming the matrix is an affine transformation.
			 *
			 * @param {Matrix4} matrix The matrix.
			 * @param {Cartesian3} result The object onto which to store the result.
			 * @returns {Cartesian3} The modified result parameter.
			 *
			 * @see Matrix4::multiplyByScale
			 * @see Matrix4::multiplyByUniformScale
			 * @see static Matrix4 fromScale
			 * @see static Matrix4 fromUniformScale
			 * @see Matrix4::setScale
			 * @see Matrix4::setUniformScale
			 */
			static Cartesian3 getScale(Matrix4& matrix) {
				Cartesian3 result;
				result.x = Cartesian3::magnitude(
					Cartesian3::fromElements(matrix.data[0], matrix.data[1], matrix.data[2])
				);
				result.y = Cartesian3::magnitude(
					Cartesian3::fromElements(matrix.data[4], matrix.data[5], matrix.data[6])
				);
				result.z = Cartesian3::magnitude(
					Cartesian3::fromElements(matrix.data[8], matrix.data[9], matrix.data[10])
				);
				return result;
			};


			/**
			 * Computes the maximum scale assuming the matrix is an affine transformation.
			 * The maximum scale is the maximum length of the column vectors.
			 *
			 * @param {Matrix4} matrix The matrix.
			 * @returns {number} The maximum scale.
			 */
			static double getMaximumScale(Matrix4& matrix) {
				auto scaleScratch3 = Matrix4::getScale(matrix);
				return Cartesian3::maximumComponent(scaleScratch3);
			};

			/**
			 * Sets the rotation assuming the matrix is an affine transformation.
			 *
			 * @param {Matrix4} matrix The matrix.
			 * @param {Matrix4} rotation The rotation matrix.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @see Matrix4::getRotation
			 */
			static Matrix4 setRotation(Matrix4& matrix, Matrix4 rotation) {
				auto scale = Matrix4::getScale(matrix);
				Matrix4 result;
				result[0] = rotation[0] * scale.x;
				result[1] = rotation[1] * scale.x;
				result[2] = rotation[2] * scale.x;
				result[3] = matrix[3];

				result[4] = rotation[3] * scale.y;
				result[5] = rotation[4] * scale.y;
				result[6] = rotation[5] * scale.y;
				result[7] = matrix[7];

				result[8] = rotation[6] * scale.z;
				result[9] = rotation[7] * scale.z;
				result[10] = rotation[8] * scale.z;
				result[11] = matrix[11];

				result[12] = matrix[12];
				result[13] = matrix[13];
				result[14] = matrix[14];
				result[15] = matrix[15];

				return result;
			};

			/**
			 * Extracts the rotation matrix assuming the matrix is an affine transformation.
			 *
			 * @param {Matrix4} matrix The matrix.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @see Matrix4::setRotation
			 */
			static Matrix4 getRotation(Matrix4& matrix) {
				auto scale = Matrix4::getScale(matrix);
				Matrix4 result;
				result[0] = matrix[0] / scale.x;
				result[1] = matrix[1] / scale.x;
				result[2] = matrix[2] / scale.x;

				result[3] = matrix[4] / scale.y;
				result[4] = matrix[5] / scale.y;
				result[5] = matrix[6] / scale.y;

				result[6] = matrix[8] / scale.z;
				result[7] = matrix[9] / scale.z;
				result[8] = matrix[10] / scale.z;

				return result;
			};

			/**
			 * Computes the product of two matrices.
			 *
			 * @param {Matrix4} left The first matrix.
			 * @param {Matrix4} right The second matrix.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 multiply(Matrix4& left, Matrix4& right) {
				auto left0 = left[0];
				auto left1 = left[1];
				auto left2 = left[2];
				auto left3 = left[3];
				auto left4 = left[4];
				auto left5 = left[5];
				auto left6 = left[6];
				auto left7 = left[7];
				auto left8 = left[8];
				auto left9 = left[9];
				auto left10 = left[10];
				auto left11 = left[11];
				auto left12 = left[12];
				auto left13 = left[13];
				auto left14 = left[14];
				auto left15 = left[15];

				auto right0 = right[0];
				auto right1 = right[1];
				auto right2 = right[2];
				auto right3 = right[3];
				auto right4 = right[4];
				auto right5 = right[5];
				auto right6 = right[6];
				auto right7 = right[7];
				auto right8 = right[8];
				auto right9 = right[9];
				auto right10 = right[10];
				auto right11 = right[11];
				auto right12 = right[12];
				auto right13 = right[13];
				auto right14 = right[14];
				auto right15 = right[15];

				auto column0Row0 =
					left0 * right0 + left4 * right1 + left8 * right2 + left12 * right3;
				auto column0Row1 =
					left1 * right0 + left5 * right1 + left9 * right2 + left13 * right3;
				auto column0Row2 =
					left2 * right0 + left6 * right1 + left10 * right2 + left14 * right3;
				auto column0Row3 =
					left3 * right0 + left7 * right1 + left11 * right2 + left15 * right3;

				auto column1Row0 =
					left0 * right4 + left4 * right5 + left8 * right6 + left12 * right7;
				auto column1Row1 =
					left1 * right4 + left5 * right5 + left9 * right6 + left13 * right7;
				auto column1Row2 =
					left2 * right4 + left6 * right5 + left10 * right6 + left14 * right7;
				auto column1Row3 =
					left3 * right4 + left7 * right5 + left11 * right6 + left15 * right7;

				auto column2Row0 =
					left0 * right8 + left4 * right9 + left8 * right10 + left12 * right11;
				auto column2Row1 =
					left1 * right8 + left5 * right9 + left9 * right10 + left13 * right11;
				auto column2Row2 =
					left2 * right8 + left6 * right9 + left10 * right10 + left14 * right11;
				auto column2Row3 =
					left3 * right8 + left7 * right9 + left11 * right10 + left15 * right11;

				auto column3Row0 =
					left0 * right12 + left4 * right13 + left8 * right14 + left12 * right15;
				auto column3Row1 =
					left1 * right12 + left5 * right13 + left9 * right14 + left13 * right15;
				auto column3Row2 =
					left2 * right12 + left6 * right13 + left10 * right14 + left14 * right15;
				auto column3Row3 =
					left3 * right12 + left7 * right13 + left11 * right14 + left15 * right15;
				Matrix4 result;
				result[0] = column0Row0;
				result[1] = column0Row1;
				result[2] = column0Row2;
				result[3] = column0Row3;
				result[4] = column1Row0;
				result[5] = column1Row1;
				result[6] = column1Row2;
				result[7] = column1Row3;
				result[8] = column2Row0;
				result[9] = column2Row1;
				result[10] = column2Row2;
				result[11] = column2Row3;
				result[12] = column3Row0;
				result[13] = column3Row1;
				result[14] = column3Row2;
				result[15] = column3Row3;
				return result;
			};

			/**
			 * Computes the sum of two matrices.
			 *
			 * @param {Matrix4} left The first matrix.
			 * @param {Matrix4} right The second matrix.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 add(Matrix4& left, Matrix4& right) {
				Matrix4 result;

				result[0] = left[0] + right[0];
				result[1] = left[1] + right[1];
				result[2] = left[2] + right[2];
				result[3] = left[3] + right[3];
				result[4] = left[4] + right[4];
				result[5] = left[5] + right[5];
				result[6] = left[6] + right[6];
				result[7] = left[7] + right[7];
				result[8] = left[8] + right[8];
				result[9] = left[9] + right[9];
				result[10] = left[10] + right[10];
				result[11] = left[11] + right[11];
				result[12] = left[12] + right[12];
				result[13] = left[13] + right[13];
				result[14] = left[14] + right[14];
				result[15] = left[15] + right[15];
				return result;
			};

			/**
			 * Computes the difference of two matrices.
			 *
			 * @param {Matrix4} left The first matrix.
			 * @param {Matrix4} right The second matrix.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 subtract(Matrix4& left, Matrix4& right) {
				Matrix4 result;
				result[0] = left[0] - right[0];
				result[1] = left[1] - right[1];
				result[2] = left[2] - right[2];
				result[3] = left[3] - right[3];
				result[4] = left[4] - right[4];
				result[5] = left[5] - right[5];
				result[6] = left[6] - right[6];
				result[7] = left[7] - right[7];
				result[8] = left[8] - right[8];
				result[9] = left[9] - right[9];
				result[10] = left[10] - right[10];
				result[11] = left[11] - right[11];
				result[12] = left[12] - right[12];
				result[13] = left[13] - right[13];
				result[14] = left[14] - right[14];
				result[15] = left[15] - right[15];
				return result;
			};

			/**
			 * Computes the product of two matrices assuming the matrices are affine transformation matrices,
			 * where the upper left 3x3 elements are any matrix, and
			 * the upper three elements in the fourth column are the translation.
			 * The bottom row is assumed to be [0, 0, 0, 1].
			 * The matrix is not verified to be in the proper form.
			 * This method is faster than computing the product for general 4x4
			 * matrices using {@link Matrix4.multiply}.
			 *
			 * @param {Matrix4} left The first matrix.
			 * @param {Matrix4} right The second matrix.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @example
			 * const m1 = new Cesium.Matrix4(1.0, 6.0, 7.0, 0.0, 2.0, 5.0, 8.0, 0.0, 3.0, 4.0, 9.0, 0.0, 0.0, 0.0, 0.0, 1.0);
			 * const m2 = Cesium.Transforms.eastNorthUpToFixedFrame(new Cesium.Cartesian3(1.0, 1.0, 1.0));
			 * const m3 = Cesium.Matrix4.multiplyTransformation(m1, m2, new Cesium.Matrix4());
			 */
			static Matrix4 multiplyTransformation(Matrix4 left, Matrix4 right) {
				auto left0 = left[0];
				auto left1 = left[1];
				auto left2 = left[2];
				auto left4 = left[4];
				auto left5 = left[5];
				auto left6 = left[6];
				auto left8 = left[8];
				auto left9 = left[9];
				auto left10 = left[10];
				auto left12 = left[12];
				auto left13 = left[13];
				auto left14 = left[14];

				auto right0 = right[0];
				auto right1 = right[1];
				auto right2 = right[2];
				auto right4 = right[4];
				auto right5 = right[5];
				auto right6 = right[6];
				auto right8 = right[8];
				auto right9 = right[9];
				auto right10 = right[10];
				auto right12 = right[12];
				auto right13 = right[13];
				auto right14 = right[14];

				auto column0Row0 = left0 * right0 + left4 * right1 + left8 * right2;
				auto column0Row1 = left1 * right0 + left5 * right1 + left9 * right2;
				auto column0Row2 = left2 * right0 + left6 * right1 + left10 * right2;

				auto column1Row0 = left0 * right4 + left4 * right5 + left8 * right6;
				auto column1Row1 = left1 * right4 + left5 * right5 + left9 * right6;
				auto column1Row2 = left2 * right4 + left6 * right5 + left10 * right6;

				auto column2Row0 = left0 * right8 + left4 * right9 + left8 * right10;
				auto column2Row1 = left1 * right8 + left5 * right9 + left9 * right10;
				auto column2Row2 = left2 * right8 + left6 * right9 + left10 * right10;

				auto column3Row0 =
					left0 * right12 + left4 * right13 + left8 * right14 + left12;
				auto column3Row1 =
					left1 * right12 + left5 * right13 + left9 * right14 + left13;
				auto column3Row2 =
					left2 * right12 + left6 * right13 + left10 * right14 + left14;
				Matrix4 result;

				result[0] = column0Row0;
				result[1] = column0Row1;
				result[2] = column0Row2;
				result[3] = 0.0;
				result[4] = column1Row0;
				result[5] = column1Row1;
				result[6] = column1Row2;
				result[7] = 0.0;
				result[8] = column2Row0;
				result[9] = column2Row1;
				result[10] = column2Row2;
				result[11] = 0.0;
				result[12] = column3Row0;
				result[13] = column3Row1;
				result[14] = column3Row2;
				result[15] = 1.0;
				return result;
			};

			/**
			 * Computes the product of a matrix and a column vector.
			 *
			 * @param {Matrix4} matrix The matrix.
			 * @param {Cartesian3} cartesian The column.
			 * @param {Cartesian3} result The object onto which to store the result.
			 * @returns {Cartesian3} The modified result parameter.
			 */
			static Cartesian4 multiplyByVector(Matrix4& matrix, Cartesian4 cartesian) {
				auto vX = cartesian.x;
				auto vY = cartesian.y;
				auto vZ = cartesian.z;
				auto vW = cartesian.w;

				auto x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12] * vW;
				auto y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13] * vW;
				auto z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14] * vW;
				auto w = matrix[3] * vX + matrix[7] * vY + matrix[11] * vZ + matrix[15] * vW;
				Cartesian4 result;
				result.x = x;
				result.y = y;
				result.z = z;
				result.w = w;
				return result;
			};

			/**
			 * Computes the product of a matrix and a {@link Cartesian3}.  This is equivalent to calling {@link Matrix4.multiplyByVector}
			 * with a {@link Cartesian4} with a <code>w</code> component of zero.
			 *
			 * @param {Matrix4} matrix The matrix.
			 * @param {Cartesian3} cartesian The point.
			 * @param {Cartesian3} result The object onto which to store the result.
			 * @returns {Cartesian3} The modified result parameter.
			 *
			 * @example
			 * const p = new Cesium.Cartesian3(1.0, 2.0, 3.0);
			 * const result = Cesium.Matrix4.multiplyByPointAsVector(matrix, p, new Cesium.Cartesian3());
			 * // A shortcut for
			 * //   Cartesian3 p = ...
			 * //   Cesium.Matrix4.multiplyByVector(matrix, new Cesium.Cartesian4(p.x, p.y, p.z, 0.0), result);
			 */
			static Cartesian3 multiplyByPointAsVector(Matrix4 matrix, Cartesian3 cartesian) {
				auto vX = cartesian.x;
				auto vY = cartesian.y;
				auto vZ = cartesian.z;

				auto x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ;
				auto y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ;
				auto z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ;
				Cartesian3 result;
				result.x = x;
				result.y = y;
				result.z = z;
				return result;
			};

			/**
			 * Computes the product of a matrix and a {@link Cartesian3}. This is equivalent to calling {@link Matrix4.multiplyByVector}
			 * with a {@link Cartesian4} with a <code>w</code> component of 1, but returns a {@link Cartesian3} instead of a {@link Cartesian4}.
			 *
			 * @param {Matrix4} matrix The matrix.
			 * @param {Cartesian3} cartesian The point.
			 * @param {Cartesian3} result The object onto which to store the result.
			 * @returns {Cartesian3} The modified result parameter.
			 *
			 * @example
			 * const p = new Cesium.Cartesian3(1.0, 2.0, 3.0);
			 * const result = Cesium.Matrix4.multiplyByPoint(matrix, p, new Cesium.Cartesian3());
			 */
			static Cartesian3 multiplyByPoint(Matrix4 matrix, Cartesian3 cartesian) {
				Cartesian3 result;
				auto vX = cartesian.x;
				auto vY = cartesian.y;
				auto vZ = cartesian.z;

				auto x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12];
				auto y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13];
				auto z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14];

				result.x = x;
				result.y = y;
				result.z = z;
				return result;
			};

			/**
			 * Computes the product of a matrix and a scalar.
			 *
			 * @param {Matrix4} matrix The matrix.
			 * @param {number} scalar The number to multiply by.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 multiplyByScalar(Matrix4& matrix, double scalar) {
				Matrix4 result;
				result[0] = matrix[0] * scalar;
				result[1] = matrix[1] * scalar;
				result[2] = matrix[2] * scalar;
				result[3] = matrix[3] * scalar;
				result[4] = matrix[4] * scalar;
				result[5] = matrix[5] * scalar;
				result[6] = matrix[6] * scalar;
				result[7] = matrix[7] * scalar;
				result[8] = matrix[8] * scalar;
				result[9] = matrix[9] * scalar;
				result[10] = matrix[10] * scalar;
				result[11] = matrix[11] * scalar;
				result[12] = matrix[12] * scalar;
				result[13] = matrix[13] * scalar;
				result[14] = matrix[14] * scalar;
				result[15] = matrix[15] * scalar;
				return result;
			};


			/**
			 * Multiplies a transformation matrix (with a bottom row of <code>[0.0, 0.0, 0.0, 1.0]</code>)
			 * by a 3x3 rotation matrix.  This is an optimization
			 * for <code>Matrix4.multiply(m, Matrix4.fromRotationTranslation(rotation), m);</code> with less allocations and arithmetic operations.
			 *
			 * @param {Matrix4} matrix The matrix on the left-hand side.
			 * @param {Matrix3} rotation The 3x3 rotation matrix on the right-hand side.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @example
			 * // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromRotationTranslation(rotation), m);
			 * Cesium.Matrix4.multiplyByMatrix3(m, rotation, m);
			 */
			static Matrix4 multiplyByMatrix3(Matrix4 matrix, Matrix3 rotation) {
				auto left0 = matrix[0];
				auto left1 = matrix[1];
				auto left2 = matrix[2];
				auto left4 = matrix[4];
				auto left5 = matrix[5];
				auto left6 = matrix[6];
				auto left8 = matrix[8];
				auto left9 = matrix[9];
				auto left10 = matrix[10];

				auto right0 = rotation[0];
				auto right1 = rotation[1];
				auto right2 = rotation[2];
				auto right4 = rotation[3];
				auto right5 = rotation[4];
				auto right6 = rotation[5];
				auto right8 = rotation[6];
				auto right9 = rotation[7];
				auto right10 = rotation[8];

				auto column0Row0 = left0 * right0 + left4 * right1 + left8 * right2;
				auto column0Row1 = left1 * right0 + left5 * right1 + left9 * right2;
				auto column0Row2 = left2 * right0 + left6 * right1 + left10 * right2;

				auto column1Row0 = left0 * right4 + left4 * right5 + left8 * right6;
				auto column1Row1 = left1 * right4 + left5 * right5 + left9 * right6;
				auto column1Row2 = left2 * right4 + left6 * right5 + left10 * right6;

				auto column2Row0 = left0 * right8 + left4 * right9 + left8 * right10;
				auto column2Row1 = left1 * right8 + left5 * right9 + left9 * right10;
				auto column2Row2 = left2 * right8 + left6 * right9 + left10 * right10;
				Matrix4 result;
				result[0] = column0Row0;
				result[1] = column0Row1;
				result[2] = column0Row2;
				result[3] = 0.0;
				result[4] = column1Row0;
				result[5] = column1Row1;
				result[6] = column1Row2;
				result[7] = 0.0;
				result[8] = column2Row0;
				result[9] = column2Row1;
				result[10] = column2Row2;
				result[11] = 0.0;
				result[12] = matrix[12];
				result[13] = matrix[13];
				result[14] = matrix[14];
				result[15] = matrix[15];
				return result;
			};

			/**
			 * Multiplies a transformation matrix (with a bottom row of <code>[0.0, 0.0, 0.0, 1.0]</code>)
			 * by an implicit translation matrix defined by a {@link Cartesian3}.  This is an optimization
			 * for <code>Matrix4.multiply(m, Matrix4.fromTranslation(position), m);</code> with less allocations and arithmetic operations.
			 *
			 * @param {Matrix4} matrix The matrix on the left-hand side.
			 * @param {Cartesian3} translation The translation on the right-hand side.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @example
			 * // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromTranslation(position), m);
			 * Cesium.Matrix4.multiplyByTranslation(m, position, m);
			 */
			static Matrix4 multiplyByTranslation(Matrix4 matrix, Cartesian3 translation) {
				Matrix4 result;

				auto x = translation.x;
				auto y = translation.y;
				auto z = translation.z;

				auto tx = x * matrix[0] + y * matrix[4] + z * matrix[8] + matrix[12];
				auto ty = x * matrix[1] + y * matrix[5] + z * matrix[9] + matrix[13];
				auto tz = x * matrix[2] + y * matrix[6] + z * matrix[10] + matrix[14];

				result[0] = matrix[0];
				result[1] = matrix[1];
				result[2] = matrix[2];
				result[3] = matrix[3];
				result[4] = matrix[4];
				result[5] = matrix[5];
				result[6] = matrix[6];
				result[7] = matrix[7];
				result[8] = matrix[8];
				result[9] = matrix[9];
				result[10] = matrix[10];
				result[11] = matrix[11];
				result[12] = tx;
				result[13] = ty;
				result[14] = tz;
				result[15] = matrix[15];
				return result;
			};

			/**
			 * Computes the product of a matrix times a (non-uniform) scale, as if the scale were a scale matrix.
			 *
			 * @param {Matrix4} matrix The matrix on the left-hand side.
			 * @param {Cartesian3} scale The non-uniform scale on the right-hand side.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 *
			 * @example
			 * // Instead of Cesium.Matrix4::multiply(m, Cesium.static Matrix4 fromScale(scale), m);
			 * Cesium.Matrix4::multiplyByScale(m, scale, m);
			 *
			 * @see Matrix4::multiplyByUniformScale
			 * @see static Matrix4 fromScale
			 * @see static Matrix4 fromUniformScale
			 * @see Matrix4::setScale
			 * @see Matrix4::setUniformScale
			 * @see Matrix4::getScale
			 */
			static Matrix4 multiplyByScale(Matrix4& matrix, Cartesian3 scale) {

				Matrix4 result;
				auto scaleX = scale.x;
				auto scaleY = scale.y;
				auto scaleZ = scale.z;

				// Faster than Cartesian3.equals
				if (scaleX == 1.0 && scaleY == 1.0 && scaleZ == 1.0) {
					return matrix;
				}

				result[0] = scaleX * matrix[0];
				result[1] = scaleX * matrix[1];
				result[2] = scaleX * matrix[2];
				result[3] = matrix[3];

				result[4] = scaleY * matrix[4];
				result[5] = scaleY * matrix[5];
				result[6] = scaleY * matrix[6];
				result[7] = matrix[7];

				result[8] = scaleZ * matrix[8];
				result[9] = scaleZ * matrix[9];
				result[10] = scaleZ * matrix[10];
				result[11] = matrix[11];

				result[12] = matrix[12];
				result[13] = matrix[13];
				result[14] = matrix[14];
				result[15] = matrix[15];

				return result;
			};

			/**
			 * Computes the product of a matrix times a uniform scale, as if the scale were a scale matrix.
			 *
			 * @param {Matrix4} matrix The matrix on the left-hand side.
			 * @param {number} scale The uniform scale on the right-hand side.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @example
			 * // Instead of Cesium.Matrix4::multiply(m, Cesium.static Matrix4 fromUniformScale(scale), m);
			 * Cesium.Matrix4::multiplyByUniformScale(m, scale, m);
			 *
			 * @see Matrix4::multiplyByScale
			 * @see static Matrix4 fromScale
			 * @see static Matrix4 fromUniformScale
			 * @see Matrix4::setScale
			 * @see Matrix4::setUniformScale
			 * @see Matrix4::getScale
			 */
			static Matrix4 multiplyByUniformScale(Matrix4& matrix, double scale) {
				Matrix4 result;

				result[0] = matrix[0] * scale;
				result[1] = matrix[1] * scale;
				result[2] = matrix[2] * scale;
				result[3] = matrix[3];

				result[4] = matrix[4] * scale;
				result[5] = matrix[5] * scale;
				result[6] = matrix[6] * scale;
				result[7] = matrix[7];

				result[8] = matrix[8] * scale;
				result[9] = matrix[9] * scale;
				result[10] = matrix[10] * scale;
				result[11] = matrix[11];

				result[12] = matrix[12];
				result[13] = matrix[13];
				result[14] = matrix[14];
				result[15] = matrix[15];

				return result;
			};

			/**
			 * Creates a negated copy of the provided matrix.
			 *
			 * @param {Matrix4} matrix The matrix to negate.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 negate(Matrix4& matrix) {
				Matrix4 result;

				result[0] = -matrix[0];
				result[1] = -matrix[1];
				result[2] = -matrix[2];
				result[3] = -matrix[3];
				result[4] = -matrix[4];
				result[5] = -matrix[5];
				result[6] = -matrix[6];
				result[7] = -matrix[7];
				result[8] = -matrix[8];
				result[9] = -matrix[9];
				result[10] = -matrix[10];
				result[11] = -matrix[11];
				result[12] = -matrix[12];
				result[13] = -matrix[13];
				result[14] = -matrix[14];
				result[15] = -matrix[15];
				return result;
			};

			/**
			 * Gets the translation portion of the provided matrix, assuming the matrix is an affine transformation matrix.
			 *
			 * @param {Matrix4} matrix The matrix to use.
			 * @param {Cartesian3} result The object onto which to store the result.
			 * @returns {Cartesian3} The modified result parameter.
			 */
			static Cartesian3 getTranslation(Matrix4 matrix) {
				Cartesian3 result;
				result.x = matrix[12];
				result.y = matrix[13];
				result.z = matrix[14];
				return result;
			};

			/**
			 * Gets the upper left 3x3 matrix of the provided matrix.
			 *
			 * @param {Matrix4} matrix The matrix to use.
			 * @param {Matrix3} result The object onto which to store the result.
			 * @returns {Matrix3} The modified result parameter.
			 *
			 * @example
			 * // returns a Matrix3 instance from a Matrix4 instance
			 *
			 * // m = [10.0, 14.0, 18.0, 22.0]
			 * //     [11.0, 15.0, 19.0, 23.0]
			 * //     [12.0, 16.0, 20.0, 24.0]
			 * //     [13.0, 17.0, 21.0, 25.0]
			 *
			 * const b = new Cesium.Matrix3();
			 * Cesium.Matrix4.getMatrix3(m,b);
			 *
			 * // b = [10.0, 14.0, 18.0]
			 * //     [11.0, 15.0, 19.0]
			 * //     [12.0, 16.0, 20.0]
			 */
			static Matrix3 getMatrix3(Matrix4 matrix) {
				Matrix3 result;

				result[0] = matrix[0];
				result[1] = matrix[1];
				result[2] = matrix[2];
				result[3] = matrix[4];
				result[4] = matrix[5];
				result[5] = matrix[6];
				result[6] = matrix[8];
				result[7] = matrix[9];
				result[8] = matrix[10];
				return result;
			};

			/**
			 * Computes the transpose of the provided matrix.
			 *
			 * @param {Matrix4} matrix The matrix to transpose.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 transpose(Matrix4& matrix) {
				Matrix4 result;

				auto matrix1 = matrix[1];
				auto matrix2 = matrix[2];
				auto matrix3 = matrix[3];
				auto matrix6 = matrix[6];
				auto matrix7 = matrix[7];
				auto matrix11 = matrix[11];

				result[0] = matrix[0];
				result[1] = matrix[4];
				result[2] = matrix[8];
				result[3] = matrix[12];
				result[4] = matrix1;
				result[5] = matrix[5];
				result[6] = matrix[9];
				result[7] = matrix[13];
				result[8] = matrix2;
				result[9] = matrix6;
				result[10] = matrix[10];
				result[11] = matrix[14];
				result[12] = matrix3;
				result[13] = matrix7;
				result[14] = matrix11;
				result[15] = matrix[15];
				return result;
			};

			/**
			 * Computes a matrix, which contains the absolute (unsigned) values of the provided matrix's elements.
			 *
			 * @param {Matrix4} matrix The matrix with signed elements.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 abs(Matrix4& matrix) {
				Matrix4 result;
				result[0] = std::abs(matrix[0]);
				result[1] = std::abs(matrix[1]);
				result[2] = std::abs(matrix[2]);
				result[3] = std::abs(matrix[3]);
				result[4] = std::abs(matrix[4]);
				result[5] = std::abs(matrix[5]);
				result[6] = std::abs(matrix[6]);
				result[7] = std::abs(matrix[7]);
				result[8] = std::abs(matrix[8]);
				result[9] = std::abs(matrix[9]);
				result[10] = std::abs(matrix[10]);
				result[11] = std::abs(matrix[11]);
				result[12] = std::abs(matrix[12]);
				result[13] = std::abs(matrix[13]);
				result[14] = std::abs(matrix[14]);
				result[15] = std::abs(matrix[15]);
				return result;
			};

			/**
			 * Computes the inverse of the provided matrix.
			 *
			 * @param {Matrix4} matrix The matrix to invert.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 *
			 * @exception {DeveloperError} matrix is not invertible.
			 */
			static Matrix4 inverse(Matrix4 matrix) {
				Matrix4 result;
				auto src0 = matrix[0];
				auto src1 = matrix[4];
				auto src2 = matrix[8];
				auto src3 = matrix[12];
				auto src4 = matrix[1];
				auto src5 = matrix[5];
				auto src6 = matrix[9];
				auto src7 = matrix[13];
				auto src8 = matrix[2];
				auto src9 = matrix[6];
				auto src10 = matrix[10];
				auto src11 = matrix[14];
				auto src12 = matrix[3];
				auto src13 = matrix[7];
				auto src14 = matrix[11];
				auto src15 = matrix[15];

				// calculate pairs for first 8 elements (cofactors)
				auto tmp0 = src10 * src15;
				auto tmp1 = src11 * src14;
				auto tmp2 = src9 * src15;
				auto tmp3 = src11 * src13;
				auto tmp4 = src9 * src14;
				auto tmp5 = src10 * src13;
				auto tmp6 = src8 * src15;
				auto tmp7 = src11 * src12;
				auto tmp8 = src8 * src14;
				auto tmp9 = src10 * src12;
				auto tmp10 = src8 * src13;
				auto tmp11 = src9 * src12;

				// calculate first 8 elements (cofactors)
				auto dst0 =
					tmp0 * src5 +
					tmp3 * src6 +
					tmp4 * src7 -
					(tmp1 * src5 + tmp2 * src6 + tmp5 * src7);
				auto dst1 =
					tmp1 * src4 +
					tmp6 * src6 +
					tmp9 * src7 -
					(tmp0 * src4 + tmp7 * src6 + tmp8 * src7);
				auto dst2 =
					tmp2 * src4 +
					tmp7 * src5 +
					tmp10 * src7 -
					(tmp3 * src4 + tmp6 * src5 + tmp11 * src7);
				auto dst3 =
					tmp5 * src4 +
					tmp8 * src5 +
					tmp11 * src6 -
					(tmp4 * src4 + tmp9 * src5 + tmp10 * src6);
				auto dst4 =
					tmp1 * src1 +
					tmp2 * src2 +
					tmp5 * src3 -
					(tmp0 * src1 + tmp3 * src2 + tmp4 * src3);
				auto dst5 =
					tmp0 * src0 +
					tmp7 * src2 +
					tmp8 * src3 -
					(tmp1 * src0 + tmp6 * src2 + tmp9 * src3);
				auto dst6 =
					tmp3 * src0 +
					tmp6 * src1 +
					tmp11 * src3 -
					(tmp2 * src0 + tmp7 * src1 + tmp10 * src3);
				auto dst7 =
					tmp4 * src0 +
					tmp9 * src1 +
					tmp10 * src2 -
					(tmp5 * src0 + tmp8 * src1 + tmp11 * src2);

				// calculate pairs for second 8 elements (cofactors)
				tmp0 = src2 * src7;
				tmp1 = src3 * src6;
				tmp2 = src1 * src7;
				tmp3 = src3 * src5;
				tmp4 = src1 * src6;
				tmp5 = src2 * src5;
				tmp6 = src0 * src7;
				tmp7 = src3 * src4;
				tmp8 = src0 * src6;
				tmp9 = src2 * src4;
				tmp10 = src0 * src5;
				tmp11 = src1 * src4;

				// calculate second 8 elements (cofactors)
				auto dst8 =
					tmp0 * src13 +
					tmp3 * src14 +
					tmp4 * src15 -
					(tmp1 * src13 + tmp2 * src14 + tmp5 * src15);
				auto dst9 =
					tmp1 * src12 +
					tmp6 * src14 +
					tmp9 * src15 -
					(tmp0 * src12 + tmp7 * src14 + tmp8 * src15);
				auto dst10 =
					tmp2 * src12 +
					tmp7 * src13 +
					tmp10 * src15 -
					(tmp3 * src12 + tmp6 * src13 + tmp11 * src15);
				auto dst11 =
					tmp5 * src12 +
					tmp8 * src13 +
					tmp11 * src14 -
					(tmp4 * src12 + tmp9 * src13 + tmp10 * src14);
				auto dst12 =
					tmp2 * src10 +
					tmp5 * src11 +
					tmp1 * src9 -
					(tmp4 * src11 + tmp0 * src9 + tmp3 * src10);
				auto dst13 =
					tmp8 * src11 +
					tmp0 * src8 +
					tmp7 * src10 -
					(tmp6 * src10 + tmp9 * src11 + tmp1 * src8);
				auto dst14 =
					tmp6 * src9 +
					tmp11 * src11 +
					tmp3 * src8 -
					(tmp10 * src11 + tmp2 * src8 + tmp7 * src9);
				auto dst15 =
					tmp10 * src10 +
					tmp4 * src8 +
					tmp9 * src9 -
					(tmp8 * src9 + tmp11 * src10 + tmp5 * src8);

				// calculate determinant
				auto det = src0 * dst0 + src1 * dst1 + src2 * dst2 + src3 * dst3;

				if (std::abs(det) < CesiumMath::EPSILON21) {
					// Special case for a zero scale matrix that can occur, for example,
					// when a model's node has a [0, 0, 0] scale.
					Cartesian4 scratchExpectedBottomRow(0.0, 0.0, 0.0, 1.0);

					if (
						Matrix3::equalsEpsilon(
							Matrix4::getMatrix3(matrix),
							Matrix3::ZERO,
							CesiumMath::EPSILON7
						) &&
						Cartesian4::equals(
							Matrix4::getRow(matrix, 3),
							scratchExpectedBottomRow
						)
						) {
						result[0] = 0.0;
						result[1] = 0.0;
						result[2] = 0.0;
						result[3] = 0.0;
						result[4] = 0.0;
						result[5] = 0.0;
						result[6] = 0.0;
						result[7] = 0.0;
						result[8] = 0.0;
						result[9] = 0.0;
						result[10] = 0.0;
						result[11] = 0.0;
						result[12] = -matrix[12];
						result[13] = -matrix[13];
						result[14] = -matrix[14];
						result[15] = 1.0;
						return result;
					}
				}

				// calculate matrix inverse
				det = 1.0 / det;

				result[0] = dst0 * det;
				result[1] = dst1 * det;
				result[2] = dst2 * det;
				result[3] = dst3 * det;
				result[4] = dst4 * det;
				result[5] = dst5 * det;
				result[6] = dst6 * det;
				result[7] = dst7 * det;
				result[8] = dst8 * det;
				result[9] = dst9 * det;
				result[10] = dst10 * det;
				result[11] = dst11 * det;
				result[12] = dst12 * det;
				result[13] = dst13 * det;
				result[14] = dst14 * det;
				result[15] = dst15 * det;
				return result;
			};

			/**
			 * Computes the inverse of the provided matrix assuming it is a proper rigid matrix,
			 * where the upper left 3x3 elements are a rotation matrix,
			 * and the upper three elements in the fourth column are the translation.
			 * The bottom row is assumed to be [0, 0, 0, 1].
			 * The matrix is not verified to be in the proper form.
			 * This method is faster than computing the inverse for a general 4x4
			 * matrix using {@link Matrix4.inverse}.
			 *
			 * @param {Matrix4} matrix The matrix to invert.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 inverseTransformation(Matrix4 matrix) {
				Matrix4 result;
				//This function is an optimized version of the below 4 lines.
				//auto rT = Matrix3.transpose(Matrix4.getMatrix3(matrix));
				//auto rTN = Matrix3.negate(rT);
				//auto rTT = Matrix3.multiplyByVector(rTN, Matrix4.getTranslation(matrix));
				//return Matrix4.fromRotationTranslation(rT, rTT, result);

				auto matrix0 = matrix[0];
				auto matrix1 = matrix[1];
				auto matrix2 = matrix[2];
				auto matrix4 = matrix[4];
				auto matrix5 = matrix[5];
				auto matrix6 = matrix[6];
				auto matrix8 = matrix[8];
				auto matrix9 = matrix[9];
				auto matrix10 = matrix[10];

				auto vX = matrix[12];
				auto vY = matrix[13];
				auto vZ = matrix[14];

				auto x = -matrix0 * vX - matrix1 * vY - matrix2 * vZ;
				auto y = -matrix4 * vX - matrix5 * vY - matrix6 * vZ;
				auto z = -matrix8 * vX - matrix9 * vY - matrix10 * vZ;

				result[0] = matrix0;
				result[1] = matrix4;
				result[2] = matrix8;
				result[3] = 0.0;
				result[4] = matrix1;
				result[5] = matrix5;
				result[6] = matrix9;
				result[7] = 0.0;
				result[8] = matrix2;
				result[9] = matrix6;
				result[10] = matrix10;
				result[11] = 0.0;
				result[12] = x;
				result[13] = y;
				result[14] = z;
				result[15] = 1.0;
				return result;
			};

			/**
			 * Computes the inverse transpose of a matrix.
			 *
			 * @param {Matrix4} matrix The matrix to transpose and invert.
			 * @param {Matrix4} result The object onto which to store the result.
			 * @returns {Matrix4} The modified result parameter.
			 */
			static Matrix4 inverseTranspose(Matrix4 matrix) {
				return Matrix4::inverse(Matrix4::transpose(matrix));
			};

			/**
			 * Compares the provided matrices componentwise and returns
			 * <code>true</code> if they are equal, <code>false</code> otherwise.
			 *
			 * @param {Matrix4} [left] The first matrix.
			 * @param {Matrix4} [right] The second matrix.
			 * @returns {boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
			 */
			static bool equals(Matrix4& left, Matrix4& right) {
				return (
					// Translation
					left[12] == right[12] &&
					left[13] == right[13] &&
					left[14] == right[14] &&
					// Rotation/scale
					left[0] == right[0] &&
					left[1] == right[1] &&
					left[2] == right[2] &&
					left[4] == right[4] &&
					left[5] == right[5] &&
					left[6] == right[6] &&
					left[8] == right[8] &&
					left[9] == right[9] &&
					left[10] == right[10] &&
					// Bottom row
					left[3] == right[3] &&
					left[7] == right[7] &&
					left[11] == right[11] &&
					left[15] == right[15]
					);
			};

			/**
			 * Compares the provided matrices componentwise and returns
			 * <code>true</code> if they are within the provided epsilon,
			 * <code>false</code> otherwise.
			 *
			 * @param {Matrix4} [left] The first matrix.
			 * @param {Matrix4} [right] The second matrix.
			 * @param {number} [epsilon=0] The epsilon to use for equality testing.
			 * @returns {boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
			 */
			static bool equalsEpsilon(Matrix4& left, Matrix4& right, double epsilon = 0.0) {
				return (
					std::abs(left[0] - right[0]) <= epsilon &&
					std::abs(left[1] - right[1]) <= epsilon &&
					std::abs(left[2] - right[2]) <= epsilon &&
					std::abs(left[3] - right[3]) <= epsilon &&
					std::abs(left[4] - right[4]) <= epsilon &&
					std::abs(left[5] - right[5]) <= epsilon &&
					std::abs(left[6] - right[6]) <= epsilon &&
					std::abs(left[7] - right[7]) <= epsilon &&
					std::abs(left[8] - right[8]) <= epsilon &&
					std::abs(left[9] - right[9]) <= epsilon &&
					std::abs(left[10] - right[10]) <= epsilon &&
					std::abs(left[11] - right[11]) <= epsilon &&
					std::abs(left[12] - right[12]) <= epsilon &&
					std::abs(left[13] - right[13]) <= epsilon &&
					std::abs(left[14] - right[14]) <= epsilon &&
					std::abs(left[15] - right[15]) <= epsilon
					);
			};

			/**
			 * An immutable Matrix4 instance initialized to the identity matrix.
			 *
			 * @type {Matrix4}
			 * @constant
			 */
			const static Matrix4 IDENTITY;

			/**
			 * An immutable Matrix4 instance initialized to the zero matrix.
			 *
			 * @type {Matrix4}
			 * @constant
			 */
			const static Matrix4 ZERO;

			/**
			 * The index into Matrix4 for column 0, row 0.
			 *
			 * @type {number}
			 * @constant
			 */
			const static int COLUMN0ROW0 = 0;

			/**
			 * The index into Matrix4 for column 0, row 1.
			 *
			 * @type {number}
			 * @constant
			 */
			const static int COLUMN0ROW1 = 1;

			/**
			 * The index into Matrix4 for column 0, row 2.
			 *
			 * @type {number}
			 * @constant
			 */
			const static int COLUMN0ROW2 = 2;

			const static int COLUMN0ROW3 = 3;

			/**
			 * The index into Matrix4 for column 1, row 0.
			 *
			 * @type {number}
			 * @constant
			 */
			const static int COLUMN1ROW0 = 4;

			/**
			 * The index into Matrix4 for column 1, row 1.
			 *
			 * @type {number}
			 * @constant
			 */
			const static int COLUMN1ROW1 = 5;

			/**
			 * The index into Matrix4 for column 1, row 2.
			 *
			 * @type {number}
			 * @constant
			 */
			const static int COLUMN1ROW2 = 6;
			const static int COLUMN1ROW3 = 7;

			/**
			 * The index into Matrix4 for column 2, row 0.
			 *
			 * @type {number}
			 * @constant
			 */
			const static int COLUMN2ROW0 = 8;
			const static int COLUMN2ROW1 = 9;
			const static int COLUMN2ROW2 = 10;
			const static int COLUMN2ROW3 = 11;


			const static int COLUMN3ROW0 = 12;
			const static int COLUMN3ROW1 = 13;
			const static int COLUMN3ROW2 = 14;
			const static int COLUMN3ROW3 = 15;

			int length()
			{
				return Matrix4::packedLength;
			}

			/**
			 * Compares this matrix to the provided matrix componentwise and returns
			 * <code>true</code> if they are equal, <code>false</code> otherwise.
			 *
			 * @param {Matrix4} [right] The right hand side matrix.
			 * @returns {boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
			 */
			bool equals(Matrix4& right) {
				return Matrix4::equals(*this, right);
			};

			/**
			 * @private
			 */
			bool equalsArray(Matrix4& matrix, DoubleVector array1, size_t offset) {
				return (
					matrix[0] == array1[offset] &&
					matrix[1] == array1[offset + 1] &&
					matrix[2] == array1[offset + 2] &&
					matrix[3] == array1[offset + 3] &&
					matrix[4] == array1[offset + 4] &&
					matrix[5] == array1[offset + 5] &&
					matrix[6] == array1[offset + 6] &&
					matrix[7] == array1[offset + 7] &&
					matrix[8] == array1[offset + 8] &&
					matrix[9] == array1[offset + 9] &&
					matrix[10] == array1[offset + 10] &&
					matrix[11] == array1[offset + 11] &&
					matrix[12] == array1[offset + 12] &&
					matrix[13] == array1[offset + 13] &&
					matrix[14] == array1[offset + 14] &&
					matrix[15] == array1[offset + 15]
					);
			};

			/**
			 * Compares this matrix to the provided matrix componentwise and returns
			 * <code>true</code> if they are within the provided epsilon,
			 * <code>false</code> otherwise.
			 *
			 * @param {Matrix4} [right] The right hand side matrix.
			 * @param {number} [epsilon=0] The epsilon to use for equality testing.
			 * @returns {boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
			 */
			bool equalsEpsilon(Matrix4& right, double epsilon) {
				return Matrix4::equalsEpsilon(*this, right, epsilon);
			};

			/**
			 * Creates a string representing this Matrix with each row being
			 * on a separate line and in the format '(column0, column1, column2)'.
			 *
			 * @returns {string} A string representing the provided Matrix with each row being on a separate line and in the format '(column0, column1, column2)'.
			 */
			String toString() {
				return str_format(
					"%f,%f,%f,%f,\n%f,%f,%f,%f,\n%f,%f,%f,%f,\n%f,%f,%f,%f\n",
					this->data[0], this->data[4], this->data[8], this->data[12],
					this->data[1], this->data[5], this->data[9], this->data[13],
					this->data[2], this->data[6], this->data[10], this->data[14],
					this->data[3], this->data[7], this->data[11], this->data[15]
				);
			}
		};
	}
}
#endif

